﻿using Games.Common;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Games.FlipBlocks
{
    class Game
    {
        private int level;
        public int Width { get; set; }
        public int Height { get; set; }
        public int ColorCount { get; set; }
        public int BroomCount { get; set; }
        public int Score { get; set; }
        private int totalColorCount = Cell.Colors.Length;
        private List<int> ids;
        private Queue<int> queue;
        private Random rd = new Random(DateTime.Now.Millisecond);
        private int?[,] map;
        private Dictionary<int, int> bombs = new Dictionary<int, int>();
        private int intMin = 3;
        private int rateBomb = 30;
        public Dictionary<int, int> GetBombs()
        {
            return bombs.ToDictionary(x => x.Key, x => x.Value);
        }
        public int Current { get; private set; }
        public int CurrentColorID { get; private set; }
        public void Init()
        {
            this.Width = 8;
            this.Height = 10;
            this.BroomCount = 3;
            this.ColorCount = 0;
            this.Score = 0;
            this.bombs.Clear();
            this.map = new int?[Width, Height];
            ids = new List<int>();
            TryAddColor();
            queue = new Queue<int>();
            while(queue.Count < 3)
            {
                var id = rd.Next(ids.Count);
                queue.Enqueue(ids[id]);
            }
            Current = 0;
            var cid = rd.Next(ids.Count);
            this.map[Current % Width, Current / Width] = CurrentColorID = ids[cid];
        }

        private void TryAddColor()
        {
            this.level = Math.Min(4, (this.Score / 1000));
            var dcount = this.level + 3;
            if(dcount != this.ColorCount)
            {
                this.ColorCount = dcount;
            }
            while(ids.Count < this.ColorCount)
            {
                var id = rd.Next(totalColorCount);
                if(!ids.Contains(id))
                {
                    ids.Add(id);
                }
            }
        }
        public List<int> Flip(Direction direction)
        {
            var next = Current;
            var cx = next % Width;
            var cy = next / Width;
            switch(direction)
            {
                case Direction.Right:
                    if(cx >= Width - 1 || this.map[cx + 1, cy] != null || this.bombs.ContainsKey(next + 1))
                        return null;
                    next += 1;
                    break;
                case Direction.Left:
                    if(cx <= 0 || this.map[cx - 1, cy] != null || this.bombs.ContainsKey(next - 1))
                        return null;
                    next -= 1;
                    break;
                case Direction.Down:
                    if(cy >= Height - 1 || this.map[cx, cy + 1] != null || this.bombs.ContainsKey(next + Width))
                        return null;
                    next += Width;
                    break;
                case Direction.Up:
                    if(cy <= 0 || this.map[cx, cy - 1] != null || this.bombs.ContainsKey(next - Width))
                        return null;
                    next -= Width;
                    break;
                default:
                    return null;
            }
            var nextColorId = queue.Dequeue();
            var id = rd.Next(ids.Count);
            queue.Enqueue(ids[id]);
            Current = next;
            this.map[Current % Width, Current / Width] = CurrentColorID = nextColorId;
            var keys = this.bombs.Keys.ToList();
            foreach(var key in keys)
            {
                this.bombs[key]--;
            }
            var around = GetSame();
            if(around.Count < 2)
            {
                var isbomb = rd.Next(rateBomb) == 0;
                if(isbomb)
                {
                    this.bombs.Add(Current, 5);
                }
            }
            return around;
        }

        public List<int> GetNexts()
        {
            return queue.ToList();
        }

        public void RemoveFromNext()
        {
            queue.Dequeue();
            var id = rd.Next(ids.Count);
            queue.Enqueue(ids[id]);
            this.BroomCount--;
        }

        private List<int> GetSame()
        {
            var visitors = new List<int> { Current };
            var sames = new List<int>();
            var color = this.map[Current % Width, Current / Width].Value;
            var around = GetAround(Current, color, visitors);
            while(around.Any())
            {
                sames.AddRange(around);
                around = around.SelectMany(x => GetAround(x, color, visitors)).ToList();
            }
            if(sames.Count >= 2)
            {
                this.Score += sames.Count * sames.Count;
                TryAddColor();
                foreach(var id in sames)
                {
                    this.map[id % Width, id / Width] = null;
                }
                if(sames.Count > intMin)
                {
                    this.BroomCount += sames.Count - intMin;
                }
            }
            return sames;
        }
        public (List<int>, List<int>) Bomb()
        {
            if(this.bombs.Any(x => x.Value == 0))
            {
                var bomb = this.bombs.First(x => x.Value == 0);
                var bids = new List<int>();
                var around = GetAround8(bomb.Key, bids);
                bids = bids.Distinct().ToList();
                around = around.Where(x => this.map[x % Width, x / Width] != null).Union(bids).Distinct().ToList();
                foreach(var cid in bids)
                {
                    this.map[cid % Width, cid / Width] = null;
                    this.bombs.Remove(cid);
                }
                foreach(var item in around)
                {
                    this.map[item % Width, item / Width] = null;
                }
                this.Score += around.Count * 5;
                return (bids, around);
            }
            return (null, null);
        }
        private List<int> GetAround8(int id, List<int> bids)
        {
            bids.Add(id);
            var list = new List<int>();
            if(id % Width > 0)
            {
                list.Add(id - 1);
            }
            if(id % Width < Width - 1)
            {
                list.Add(id + 1);
            }
            if(id / Width > 0)
            {
                list.Add(id - Width);
            }
            if(id / Width < Height - 1)
            {
                list.Add(id + Width);
            }
            if(id % Width > 0 && id / Width > 0)
            {
                list.Add(id - 1 - Width);
            }
            if(id % Width > 0 && id / Width < Height - 1)
            {
                list.Add(id - 1 + Width);
            }
            if(id % Width < Width - 1 && id / Width > 0)
            {
                list.Add(id + 1 - Width);
            }
            if(id % Width < Width - 1 && id / Width < Height - 1)
            {
                list.Add(id + 1 + Width);
            }
            var nlist = list.Except(bids).ToList();
            foreach(var item in nlist)
            {
                if(this.bombs.ContainsKey(item) && !bids.Contains(item))
                {
                    var ar = GetAround8(item, bids);
                    foreach(var a in ar)
                    {
                        if(!list.Contains(a))
                        {
                            list.Add(a);
                        }
                    }
                }
            }
            return list;
        }
        private List<int> GetAround(int id, int color, List<int> visitors)
        {
            var list = new List<int>();
            if(id % Width > 0 && !visitors.Contains(id - 1) && this.map[id % Width - 1, id / Width] == color)
            {
                list.Add(id - 1);
            }
            if(id % Width < Width - 1 && !visitors.Contains(id + 1) && this.map[id % Width + 1, id / Width] == color)
            {
                list.Add(id + 1);
            }
            if(id / Width > 0 && !visitors.Contains(id - Width) && this.map[id % Width, id / Width - 1] == color)
            {
                list.Add(id - Width);
            }
            if(id / Width < Height - 1 && !visitors.Contains(id + Width) && this.map[id % Width, id / Width + 1] == color)
            {
                list.Add(id + Width);
            }
            visitors.AddRange(list);
            return list;
        }

        public bool IsOver()
        {
            if(Current % Width > 0 && this.map[Current % Width - 1, Current / Width] == null && !this.bombs.ContainsKey(Current - 1))
            {
                return false;
            }
            if(Current % Width < Width - 1 && this.map[Current % Width + 1, Current / Width] == null && !this.bombs.ContainsKey(Current + 1))
            {
                return false;
            }
            if(Current / Width > 0 && this.map[Current % Width, Current / Width - 1] == null && !this.bombs.ContainsKey(Current - Width))
            {
                return false;
            }
            if(Current / Width < Height - 1 && this.map[Current % Width, Current / Width + 1] == null && !this.bombs.ContainsKey(Current + Width))
            {
                return false;
            }
            return true;
        }
    }
}
